package br.ufrj.xml.model.persistence.dao.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import br.ufrj.xml.model.persistence.dao.DataAccessException;

public abstract class GenericJdbcDAO<T> implements RowMapper<T> {
 
  private Connection connection;
 
  public GenericJdbcDAO(Connection connection) {
    this.connection = connection;
  }
 
  protected Connection getConnection() {
    return connection;
  }
 
  protected <R> List<R> query(String sql, Object[] parameters, int[] parameterTypes, RowMapper<R> mapper) {
    if(parameters == null || parameterTypes == null)
      throw new NullPointerException();
    if(parameters.length != parameterTypes.length)
      throw new IllegalArgumentException();
   
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    List<R> objectList = new ArrayList<R>();
    try {
      preparedStatement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      fillPreparedStatement(preparedStatement, parameters, parameterTypes);
      resultSet = preparedStatement.executeQuery();
      while(resultSet.next()) {
        objectList.add(mapper.mapRow(resultSet));
      }
      return objectList;
    } catch(Exception e) {
      throw new DataAccessException(e);
    } finally {
      try{ if(resultSet != null) resultSet.close(); } catch(SQLException ignored) { }
      try{ if(preparedStatement != null) preparedStatement.close(); } catch(SQLException ignored) { }
    }
  }
 
  protected <R> List<R> query(String sql, Object[] parameters, int[] parameterTypes, Class<R> clazz) {
    return query(sql, parameters, parameterTypes, new OneResultMapper<R>(clazz));
  }
 
  protected <R> List<R> query(String sql, Class<R> clazz) {
    return query(sql, new Object[0], new int[0], new OneResultMapper<R>(clazz));
  }
 
  protected List<T> query(String sql, Object[] parameters, int[] parameterTypes) {
    return query(sql, parameters, parameterTypes, this);
  }
 
  protected <R> List<R> query(String sql, RowMapper<R> mapper) {
    return query(sql, new Object[0], new int[0], mapper);
  }
 
  protected List<T> query(String sql) {
    return query(sql, new Object[0], new int[0]);
  }
 
  protected <R> List<R> queryPaged(String sql, Object[] parameters, int[] parameterTypes, int startIndex, int maxResults, RowMapper<R> mapper) {
    if(parameters == null || parameterTypes == null)
      throw new NullPointerException();
    if(parameters.length != parameterTypes.length)
      throw new IllegalArgumentException();
   
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    List<R> objectList = new ArrayList<R>();
    try {
      preparedStatement = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      fillPreparedStatement(preparedStatement, parameters, parameterTypes);
      resultSet = preparedStatement.executeQuery();
      int currentCount = 0;
      if(startIndex >= 0 && resultSet.absolute(startIndex+1)) {
        do {
          objectList.add(mapper.mapRow(resultSet));
          currentCount++;
        } while(resultSet.next() && currentCount < maxResults);
      }
      return objectList;
    } catch(Exception e) {
      throw new DataAccessException(e);
    } finally {
      try{ if(resultSet != null) resultSet.close(); } catch(SQLException ignored) { }
      try{ if(preparedStatement != null) preparedStatement.close(); } catch(SQLException ignored) { }
    }
  }
 
  protected List<T> queryPaged(String sql, int startIndex, int maxResults) {
    return queryPaged(sql, new Object[0], new int[0], startIndex, maxResults, this);
  }
 
  protected List<T> queryPaged(String sql, Object[] parameters, int[] parameterTypes, int startIndex, int maxResults) {
    return queryPaged(sql, parameters, parameterTypes, startIndex, maxResults, this);
  }
 
  protected <R> R queryOneResult(String sql, Object[] parameters, int[] parameterTypes, RowMapper<R> mapper) {
    if(parameters == null || parameterTypes == null)
      throw new NullPointerException();
    if(parameters.length != parameterTypes.length)
      throw new IllegalArgumentException();
   
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
      preparedStatement = connection.prepareStatement(sql);
      fillPreparedStatement(preparedStatement, parameters, parameterTypes);
      resultSet = preparedStatement.executeQuery();
      if(resultSet.next())
        return mapper.mapRow(resultSet);
      return null;
    } catch(Exception e) {
      throw new DataAccessException(e);
    } finally {
      try{ if(resultSet != null) resultSet.close(); } catch(SQLException ignored) { }
      try{ if(preparedStatement != null) preparedStatement.close(); } catch(SQLException ignored) { }
    }
  }
 
  protected <R> List<R> queryPaged(String sql, int startIndex, int maxResults, RowMapper<R> mapper) {
    return queryPaged(sql, new Object[0], new int[0], startIndex, maxResults, mapper);
  }
 
  protected T queryOneResult(String sql, Object[] parameters, int[] parameterTypes) {
    return queryOneResult(sql, parameters, parameterTypes, this);
  }
 
  protected T queryOneResult(String sql) {
    return queryOneResult(sql, new Object[0], new int[0]);
  }
 
  protected <R> R queryOneResult(String sql, RowMapper<R> mapper) {
    return queryOneResult(sql, new Object[0], new int[0], mapper);
  }
 
  protected <R> R queryOneResult(String sql, Class<R> clazz) {
    return queryOneResult(sql, new Object[0], new int[0], new OneResultMapper<R>(clazz));
  }
 
  protected <R> R queryOneResult(String sql, Object[] parameters, int[] parameterTypes, Class<R> clazz) {
    return queryOneResult(sql, parameters, parameterTypes, new OneResultMapper<R>(clazz));
  }
 
  protected int executeUpdate(String sql, Object[] parameters, int[] parameterTypes) {
    if(parameters == null || parameterTypes == null)
      throw new NullPointerException();
    if(parameters.length != parameterTypes.length)
      throw new IllegalArgumentException();
   
    PreparedStatement preparedStatement = null;
    try {
      preparedStatement = connection.prepareStatement(sql);
      fillPreparedStatement(preparedStatement, parameters, parameterTypes);
      return preparedStatement.executeUpdate();
    } catch(Exception e) {
      throw new DataAccessException(e);
    } finally {
      try{ if(preparedStatement != null) preparedStatement.close(); } catch(SQLException ignored) { }
    }
  }
 
  protected void abstractInsert(T entity, String sql, Object[] parameters, int[] parameterTypes) {
    if(parameters == null || parameterTypes == null)
      throw new NullPointerException();
    if(parameters.length != parameterTypes.length)
      throw new IllegalArgumentException();
   
    PreparedStatement preparedStatement = null;
    ResultSet generatedKeys = null;
    try {
      preparedStatement = connection.prepareStatement(sql, Statement.NO_GENERATED_KEYS);
      fillPreparedStatement(preparedStatement, parameters, parameterTypes);
      preparedStatement.executeUpdate();
      /* TODO: POSTGRES NAO SUPORTA GERACAO DE CHAVE AUTOMATICA
      generatedKeys = preparedStatement.getGeneratedKeys();
      if(generatedKeys.next())
        setGeneratedKeys(entity, generatedKeys);
        */
    } catch(Exception e) {
      throw new DataAccessException(e);
    } finally {
      try{ if(generatedKeys != null) generatedKeys.close(); } catch(SQLException ignored) { }
      try{ if(preparedStatement != null) preparedStatement.close(); } catch(SQLException ignored) { }
    }
  }
 
  protected void setGeneratedKeys(T entity, ResultSet generatedKeys) throws SQLException {
   
  }
 
  private void fillPreparedStatement(PreparedStatement preparedStatement, Object[] parameters, int[] parameterTypes) throws SQLException {
    int type;
    Object value;
    for (int i = 0; i < parameterTypes.length; i++) {
      type = parameterTypes[i];
      value = parameters[i];
      if(value == null)
        preparedStatement.setNull(i+1, type);
      else
        preparedStatement.setObject(i+1, value, type);
    }
  }

  class OneResultMapper<E> implements RowMapper<E> {
    Class<E> clazz;
   
    public OneResultMapper(Class<E> clazz) {
      this.clazz = clazz;
    }
   
    @SuppressWarnings("unchecked")
    public E mapRow(ResultSet result) throws SQLException {
      if(clazz.equals(Byte.class))
        return (E) new Byte(result.getByte(1));
      if(clazz.equals(Short.class))
        return (E) new Short(result.getShort(1));
      if(clazz.equals(Integer.class))
        return (E) new Integer(result.getInt(1));
      if(clazz.equals(Long.class))
        return (E) new Long(result.getLong(1));
      return (E) result.getObject(1);
    }
  }

}
